﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using MMessaging.MessagingEx.Logger;
using TUtils.BasicHelper.Logging;
using TUtils.Multithreading;
using TUtils.BasicHelper;
using TUtils.Threads;
using LogX.Logging;

namespace MMessaging.MessagingEx.Common
{
	/// <summary>
	/// Repräsentiert den Verarbeitungsprozess einer empfangenen Message (OriginalMessage).
	/// </summary>
	/// <remarks>
	/// <example><code><![CDATA[
	/// // running in messageHandler thread ....
	/// 
	/// var taskM = new MTaskThread<MyMessageType>().Init(
	/// 	mySignalHandler,
	/// 	threadName,
	/// 	this,  // MessageHandler thread
	/// 	task =>
	/// 	{
	/// 		var originalMessage = task.OriginalMessage;
	/// 
	/// 		// do sth.....
	/// 
	/// 		task.WaitOnMessage(
	/// 			"my logging text",     // text written in logging file
	/// 			msg=>msg is MessageB); // wait condition
	/// 
	/// 		// do sth.....
	/// 	});
	/// 
	/// taskM.StartAsync();
	/// 
	/// // give control to that new task and wait
	/// taskM.CurrentMessage = message;
	/// this.GiveControlAwayAndWait(taskM);
	/// ]]></code></example>
	/// 
	/// Zu empfehlen ist  die Angabe eines Timeouts für den Wartevorgang
	/// 
	/// </remarks>
	/// <typeparam name="MESSAGETYPE">
	/// </typeparam>
	internal class MTaskThread<MESSAGETYPE> : MThread<MTaskThread<MESSAGETYPE>>, IMTask<MESSAGETYPE>
		where MESSAGETYPE : class
	{
		#region public types

		/// <summary>
		/// die Zustände, in denen sich dieser Thread befinden kann
		/// </summary>
		public enum StateEnum
		{
			/// <summary>
			/// Der Thread ist derzeit nicht mit der Verarbeitung einer Message beschäftigt.
			/// Er wartet im Threadpool auf seine Wiederverwendung.
			/// </summary>
			FREE,
			/// <summary>
			/// Der Thread ist gerade mit der Verarbeitung einer Message beschäftigt.
			/// </summary>
			ACTIVE,
			/// <summary>
			/// Der Thread wartet auf die Erfüllung einer Wartebedingung, um seine
			/// Messageverarbeitung weiter fortzusetzen.
			/// </summary>
			WAITING,
			/// <summary>
			/// Der Thread ist terminiert. Dies darf eigentlich nur durch einen Cancel-Vorgang 
			/// ausgelöst worden sein.
			/// </summary>
			DEAD
		}

		#endregion

		#region private fields / properties

		private IMThread MessageHandlerThread { get; set; }
		private Action<IThreadContext, SignalEnum, Exception> _signalHandler;
		private Func<MESSAGETYPE, bool> _isMessageWaitingFor = msg => false;

		private DateTime _elapseTime;

		#endregion

		#region public properties

		/// <summary>
		/// Liefert die Bedingung, auf deren Erfüllung dieser Verarbeitungsprozess wartet,
		/// um weiter fortzufahren.
		/// </summary>
		public Func<MESSAGETYPE, bool> IsMessageWaitingFor
		{
			get
			{
				return _isMessageWaitingFor;
			}
			private set
			{
				_isMessageWaitingFor = value;
			}
		}

		/// <summary>
		/// Bis zum Verarbeitungsende der Originalmessage können viele andere Messages 
		/// empfangen werden. "CurrentMessage" ist die aktuell verarbeitete Message.
		/// Zu Beginn des Verarbeitungsprozesses ist CurrentMessage == OriginalMessage.
		/// CurrentMessage kann null sein, was bedeutet, dass die Messagequeue leer ist und
		/// der Task die Gelegenheit hat, kurz aufzuwachen, um z.B. Timer zu überprüfen.
		/// </summary>
		public MESSAGETYPE CurrentMessage { get; set; }
		/// <summary>
		/// Die Message, dessen Verarbeitung die Aufgabe dieses Prozesses ist.
		/// </summary>
		public MESSAGETYPE OriginalMessage { get; private set; }
		/// <summary>
		/// Holt oder setzt den Handler für die Verarbeitung der "OriginalMessage".
		/// Die Methodensignatur ist 
		/// <code><![CDATA[
		/// void Handler(IMTask<MESSAGETYPE> task)
		/// ]]></code>
		/// </summary>
		/// <remarks>
		/// Die übergebene Methode darf nicht lange Zeit laufen ohne task.WaitOnMessage(..)
		/// aufzurufen, da das sonst die 
		/// Pseudoparallelität der anderen Message-Verarbeitungsprozesse behindern würde.
		/// Für lange Hintergrundprozesse sollte ein Hintergrundthread erzeugt werden oder (besser)
		/// einen MService erstellt werden, der auf Messages reagiert.
		/// </remarks>
		public Action<IMTask<MESSAGETYPE>> TaskMethod { get; set; }

		/// <summary>
		/// Der Zustand, in dem sich dieser Verarbeitungsprozess befindet.
		/// </summary>
		public StateEnum TaskState { get; private set; }

		#endregion

		#region constructor

		/// <summary>
		/// Konstruktor.
		/// Nicht vergessen auch Init(..) und StartAsync() aufzurufen.
		/// </summary>
		public MTaskThread()
		{
			TaskState = StateEnum.FREE;
		}

		/// <summary>
		/// HIDDEN
		/// </summary>
		private new TThreadX<MTaskThread<MESSAGETYPE>> Init(
			Action<IThreadContext, SignalEnum, Exception> signalHandler,
			Action<IThreadContext> threadMethod,
			MTaskThread<MESSAGETYPE> obj,
			string threadName
			)
		{
			throw new ApplicationException("hsdhjjbyxkjsudv6wewgg");
		}

		/// <summary>
		/// Initialisiert diesen Task vollständig.
		/// Nicht vergessen, StartAsync() aufzurufen.
		/// </summary>
		/// <param name="signalHandler"></param>
		/// <param name="threadName"></param>
		/// <param name="messageHandlerThread"></param>
		/// <param name="messageHandler"></param>
		/// <returns></returns>
		public MTaskThread<MESSAGETYPE> Init(
			Action<IThreadContext, SignalEnum, Exception> signalHandler,
			string threadName,
			IMThread messageHandlerThread,
			Action<IMTask<MESSAGETYPE>> messageHandler
			)
		{
			MessageHandlerThread = messageHandlerThread;
			TaskMethod = messageHandler;
			_signalHandler = signalHandler;
			base.Init(
				signalHandler,
				InternalThreadMethod,
				this,
				threadName);

			return this;
		}

		#endregion

		#region public overrides

		/// <summary>
		/// Überschrieben
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return string.Format(
				"thread name = {0}\n" +
				"original message = {1}\n" +
				"current message = {2}\n" +
				"task state = {3}\n",
				ThreadName,
				OriginalMessage == null ? "" : OriginalMessage.ToString(),
				CurrentMessage == null ? "" : CurrentMessage.ToString(),
				TaskState);
		}

		#endregion

		#region private methods

		/// <summary>
		/// Thread Methode. Diese ruft für jede neue "OriginalMessage" die 
		/// "TaskMethod" auf.
		/// </summary>
		/// <param name="context"></param>
		private void InternalThreadMethod(IThreadContext context)
		{
			WaitForSignal();

			try
			{
				while (!IsCanceling)
				{
					// now thread is active ...
					TaskState = StateEnum.ACTIVE;

					// process original message
					OriginalMessage = CurrentMessage;
					TLog.Instance.SetThreadAttachedLogValue(new LogValue(
						MPredefinedLoggingValueIDs.OriginalMessageID,
						() => OriginalMessage.GetHashCode().ToString(CultureInfo.InvariantCulture)));
					TLog.Instance.SetThreadAttachedLogValue(new LogValue(
						MPredefinedLoggingValueIDs.OriginalMessageContent,
						() => OriginalMessage.ToString()));
					TLog.Instance.SetThreadAttachedLogValue(new LogValue(
						MPredefinedLoggingValueIDs.CurrentMessageID,
						() => CurrentMessage != null ?
							CurrentMessage.GetHashCode().ToString(CultureInfo.InvariantCulture) :
							"null"));
					TLog.Instance.SetThreadAttachedLogValue(new LogValue(
						MPredefinedLoggingValueIDs.CurrentMessageContent,
						() => CurrentMessage != null ? CurrentMessage.ToString() : "null"));
					TLog.Instance.LogInfo(this, "task started");
					try
					{
						TaskMethod(this);
					}
					catch (MWaitException e)
					{
						try
						{
							TLog.Instance.Log(LogSeverityEnum.WARNING, this, "task aborted - reason:{0}", () => e.Reason);
						}
						catch
						{
							// oh oh this may not happen
						}
					}
#					if !THROWEXCEPTION
					catch (Exception e)
					{
						try
						{
							var exception = new ApplicationException(
								"hhjcnydhqte6 exception in task " + this.ToString(),
								e);
							var signalHandler = _signalHandler;
							if (signalHandler != null)
								signalHandler(
									context,
									SignalEnum.STOPPED_EXCEPTION,
									exception);

							LoggX.Log(LogTypeEnum.ERROR, TUtilsLogggCategories.Threads, exception);
						}
						catch
						{
							// oh oh this may not happen
						}
					}
#					endif

					// message processed ....
					if (IsCanceling)
						return;

					// now thread is FREE
					CurrentMessage = null;
					OriginalMessage = null;
					TaskState = StateEnum.FREE;
					TLog.Instance.LogInfo(this, "task stopped and is put back to pool");

					// waiting for next message
					GiveControlAwayAndWait(MessageHandlerThread);
				}
			}
			finally
			{
				TaskState = StateEnum.DEAD;
			}
		}

		#endregion

		#region public methods

		/// <summary>
		/// Blockiert, bis die übergebene Bedingung erfüllt ist oder der Thread gecancelt wird.
		/// ACHTUNG ! Diese Methode darf nicht aufgerufen werden, wenn der aufrufende Thread noch
		/// ein Synchronisationobjekt gesperrt hat.
		/// </summary>
		/// <remarks>
		/// Folgendes ist verboten:
		/// <code>
		/// lock(syncObj)
		/// {
		///		...
		///		WaitOnMessage(msg=>msg is MyMessage);
		///		...
		/// }
		/// </code>
		/// Das könnte zu einem Deadlock führen, wenn ein anderer Thread das Objekt ebenfalls 
		/// sperren möchte, während dieser Thread auf die Erfüllung der Bedingung wartet.
		/// Erlaubt ist hingegen:
		/// <code>
		/// lock(syncObj)
		/// {
		///		...
		///	}
		///	
		///	WaitOnMessage(msg=>msg is MyMessage);
		///	
		/// lock(syncObj)
		/// {
		///		...
		///	}
		/// </code>
		/// </remarks>
		/// <param name="condition">
		/// Dieser Delegate wird vom MessageHandlerThread direkt aufgerufen !
		/// MESSAGETYPE kann null sein !
		/// </param>
		/// <param name="logText">
		/// Dieser Text wird in Logging Ausgaben genutzt.
		/// </param>
		/// <returns>
		/// false, wenn der Thread terminiert wird.
		/// </returns>
		bool IMTask<MESSAGETYPE>.WaitOnMessage(string logText, Func<MESSAGETYPE, bool> condition)
		{
			if ( IsCanceling )
				return false;

			TLog.Instance.LogInfo(this,logText + " wait on message entered");

			IsMessageWaitingFor = condition;
			TaskState = StateEnum.WAITING;
			GiveControlAwayAndWait(MessageHandlerThread);
			TaskState = StateEnum.ACTIVE;

			TLog.Instance.SetThreadAttachedLogValue(new LogValue(
				MPredefinedLoggingValueIDs.CurrentMessageID,
				() => CurrentMessage != null 
					? CurrentMessage.GetHashCode().ToString(CultureInfo.InvariantCulture)
					: "null"));
			TLog.Instance.SetThreadAttachedLogValue(new LogValue(
				MPredefinedLoggingValueIDs.CurrentMessageContent,
				() => CurrentMessage != null 
					? CurrentMessage.ToString()
					: "null"));
			TLog.Instance.LogInfo(this, logText + " wait on message leaving");

			if (IsCanceling)
				return false;

			return true;
		}

		/// <summary>
		/// Blockiert, bis die übergebene Bedingung erfüllt ist, der Thread gecancelt wird oder
		/// der geg. Timeout abläuft.
		/// ACHTUNG ! Diese Methode darf nicht aufgerufen werden, wenn der aufrufende Thread noch
		/// ein Synchronisationobjekt gesperrt hat.
		/// </summary>
		/// <remarks>
		/// Folgendes ist verboten:
		/// <code>
		/// lock(syncObj)
		/// {
		///		...
		///		WaitOnMessage(msg=>msg is MyMessage);
		///		...
		/// }
		/// </code>
		/// Das könnte zu einem Deadlock führen, wenn ein anderer Thread das Objekt ebenfalls 
		/// sperren möchte, während dieser Thread auf die Erfüllung der Bedingung wartet.
		/// Erlaubt ist hingegen:
		/// <code>
		/// lock(syncObj)
		/// {
		///		...
		///	}
		///	
		///	WaitOnMessage(msg=>msg is MyMessage);
		///	
		/// lock(syncObj)
		/// {
		///		...
		///	}
		/// </code>
		/// 
		/// Die Zeit, die das Programm debuggt wird, wird der Timeout angehalten.
		/// </remarks>
		/// <param name="timeoutMilliseconds">
		/// timeout in Millisekunden.
		/// </param>
		/// <param name="condition">
		/// Dieser Delegate wird vom MessageHandlerThread direkt aufgerufen !
		/// MESSAGETYPE kann null sein !
		/// </param>
		/// <param name="logText">
		/// Dieser Text wird in Logging Ausgaben genutzt.
		/// </param>
		/// <returns>
		/// SUCCEEDED, TIMEOUT, CANCELED
		/// </returns>
		MWaitResult IMTask<MESSAGETYPE>.WaitOnMessage(string logText, int timeoutMilliseconds, Func<MESSAGETYPE, bool> condition)
		{
			var lastTickTimestamp = DateTime.Now;
			_elapseTime = lastTickTimestamp.AddMilliseconds(Convert.ToDouble(timeoutMilliseconds));

			if (!(this as IMTask<MESSAGETYPE>).WaitOnMessage(
				logText,
				// condition
				msg => 
				{
					var now = DateTime.Now;
#					if DEBUG
					if (condition(msg))
						return true;

					// wird das Programm gerade debuggt ?
					if (now > lastTickTimestamp.AddMilliseconds(MMessagingHelper.DEBUG_TIMEOUT))
					{
						_elapseTime = _elapseTime + (now - lastTickTimestamp);
						lastTickTimestamp = now;
						return false;
					}

					lastTickTimestamp = now;
					return now > _elapseTime;
#					else
					lastTickTimestamp = now;
					return condition(msg) || DateTime.Now > _elapseTime;
#					endif
				}))
			{
				if (DateTime.Now > _elapseTime)
					return MWaitResult.TIMEOUT;
				else
					return MWaitResult.CANCELED;
			}
			else
			{
				return MWaitResult.SUCCEEDED;
			}
		}

		/// <summary>
		/// Blockiert, bis die übergebene Bedingung erfüllt ist, der Thread gecancelt wird oder
		/// der geg. Timeout abläuft. Im Fehlerfall wird eine MWaitException geworfen.
		/// ACHTUNG ! Diese Methode darf nicht aufgerufen werden, wenn der aufrufende Thread noch
		/// ein Synchronisationobjekt gesperrt hat.
		/// </summary>
		/// <remarks>
		/// Folgendes ist verboten:
		/// <code>
		/// lock(syncObj)
		/// {
		///		...
		///		WaitOnMessage(msg=>msg is MyMessage);
		///		...
		/// }
		/// </code>
		/// Das könnte zu einem Deadlock führen, wenn ein anderer Thread das Objekt ebenfalls 
		/// sperren möchte, während dieser Thread auf die Erfüllung der Bedingung wartet.
		/// Erlaubt ist hingegen:
		/// <code>
		/// lock(syncObj)
		/// {
		///		...
		///	}
		///	
		///	WaitOnMessage(msg=>msg is MyMessage);
		///	
		/// lock(syncObj)
		/// {
		///		...
		///	}
		/// </code>
		/// </remarks>
		/// <param name="timeoutMilliseconds">
		/// timeout in Millisekunden.
		/// </param>
		/// <param name="logText">
		/// Dieser Text wird in Logging Ausgaben genutzt.
		/// </param>
		/// <param name="condition">
		/// Dieser Delegate wird vom MessageHandlerThread direkt aufgerufen !
		/// MESSAGETYPE kann null sein !
		/// </param>
		/// <exception cref="MWaitException">
		/// wenn der thread gecancelt wird oder der Timeout abläuft.
		/// </exception>
		void IMTask<MESSAGETYPE>.WaitOnMessageWithTimeoutException(string logText, int timeoutMilliseconds, Func<MESSAGETYPE, bool> condition)
		{
			var result = (this as IMTask<MESSAGETYPE>).WaitOnMessage(
				logText,
				timeoutMilliseconds,
				condition);

			switch(result)
			{
				case MWaitResult.SUCCEEDED:
					return;
				case MWaitResult.CANCELED:
					throw new MWaitException(MWaitException.AbortReason.THREAD_CANCELED,logText);
				case MWaitResult.TIMEOUT:
					throw new MWaitException(MWaitException.AbortReason.TIMEOUT,logText);
				default:
					throw new ApplicationException("67sdahfkjkje8qfdj " + result);
			}
		}

		void IMTask<MESSAGETYPE>.SleepWithException(int timeoutMilliseconds)
		{
			string logText = "sleep";

			switch ((this as IMTask<MESSAGETYPE>).WaitOnMessage(
				logText,
				timeoutMilliseconds,
				msg => false))
			{
				case MWaitResult.CANCELED:
					throw new MWaitException(MWaitException.AbortReason.TIMEOUT, logText);
			}
		}

		#endregion

	}
}
